package com.computer.spring.framework.context;

import com.computer.spring.framework.annotation.BYAutowired;
import com.computer.spring.framework.annotation.BYController;
import com.computer.spring.framework.annotation.BYService;
import com.computer.spring.framework.aop.BYAopConfig;
import com.computer.spring.framework.aop.BYAopProxy;
import com.computer.spring.framework.aop.BYCglibAopProxy;
import com.computer.spring.framework.aop.BYJdkDynamicAopProxy;
import com.computer.spring.framework.aop.support.BYAdvisedSupport;
import com.computer.spring.framework.beans.BYBeanWrapper;
import com.computer.spring.framework.beans.config.BYBeanDefinition;
import com.computer.spring.framework.beans.config.BYBeanPostProcessor;
import com.computer.spring.framework.beans.support.BYBeanDefinitionReader;
import com.computer.spring.framework.beans.support.BYDefaultListableBeanFactory;
import com.computer.spring.framework.core.BYBeanFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description
 * @Author MagicIce
 * @Date 2020/11/21 13:57
 */
public class BYApplicationContext extends BYDefaultListableBeanFactory implements BYBeanFactory {

    private String[] configLocations;
    private BYBeanDefinitionReader reader;

    /** 单例的 IoC 容器缓存 */
    private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>();
    /** 通用的 IoC 容器 */
    private Map<String, BYBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();

    public BYApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void refresh() throws Exception {
        //  1、定位，定位配置文件
        reader = new BYBeanDefinitionReader(this.configLocations);
        
        //  2、加载配置文件，扫描相关的类，把它们封装成 BeanDefinition
        List<BYBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
        
        //  3、注册，把配置信息放到容器里面（伪 IoC 容器）
        doRegisterBeanDefinition(beanDefinitions);
        
        //  4、把不是延时加载的类提前初始化
        doAutowired();
    }

    private void doAutowired() {
        for (Map.Entry<String, BYBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()){
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()){
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doRegisterBeanDefinition(List<BYBeanDefinition> beanDefinitions) throws Exception {
        for (BYBeanDefinition beanDefinition : beanDefinitions){
            if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "“ is exists!");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
        //  到这里为止，容器初始化完毕
    }


    /** 装饰器模式：保留原来的 OOP 关系；需要对它进行扩展、增强（为后期 AOP 打基础） */
    @Override
    public Object getBean(String beanName) throws Exception {
        BYBeanDefinition beanDefinition = super.beanDefinitionMap.get(beanName);

        try {
            //  生成通知事件
            BYBeanPostProcessor beanPostProcessor = new BYBeanPostProcessor();
            Object instance = instantiateBean(beanDefinition);
            if (null == instance){
                return null;
            }
            beanPostProcessor.postProcessBeforeInitialization(instance, beanName);

            BYBeanWrapper beanWrapper = new BYBeanWrapper(instance);
            this.factoryBeanInstanceCache.put(beanName, beanWrapper);

            beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            populateBean(beanName, instance);
            return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void populateBean(String beanName, Object instance) {
        Class clazz = instance.getClass();
        if (!(clazz.isAnnotationPresent(BYController.class) || clazz.isAnnotationPresent(BYService.class))){
            return;
        }
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields){
            if (!field.isAnnotationPresent(BYAutowired.class)){
                continue;
            }
            BYAutowired autowired = field.getAnnotation(BYAutowired.class);
            String autowiredBeanName = autowired.value().trim();

            if ("".equals(autowiredBeanName)){
                autowiredBeanName = field.getType().getName();
            }
            field.setAccessible(true);

            try {
                BYBeanWrapper beanWrapper = this.factoryBeanInstanceCache.get(autowiredBeanName);
                if (null == beanWrapper){
                    getBean(autowiredBeanName);
                }
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                //e.printStackTrace();
            } catch (Exception e) {
//                e.printStackTrace();
            }
        }

    }

    /** 传一个 BeanDefinition，就返回一个实例 Bean */
    private Object instantiateBean(BYBeanDefinition beanDefinition) {
        Object instance = null;
        String className = beanDefinition.getBeanClassName();

        try {
            if (this.factoryBeanObjectCache.containsKey(className)){
                instance = this.factoryBeanObjectCache.get(className);
            }else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();

                BYAdvisedSupport config = instantiationAopConfig(beanDefinition);
                config.setTargetClass(clazz);
                config.setTarget(instance);

                if (config.pointCutMatch()){
                    instance = createProxy(config).getProxy();
                }

                this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(), instance);
            }
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private BYAdvisedSupport instantiationAopConfig(BYBeanDefinition beanDefinition) throws Exception{
        BYAopConfig config = new BYAopConfig();
        config.setPointCut(reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new BYAdvisedSupport(config);
    }

    private BYAopProxy createProxy(BYAdvisedSupport config){
        Class targetClass = config.getTargetClass();
        if (targetClass.getInterfaces().length > 0){
            return new BYJdkDynamicAopProxy(config);
        }
        return new BYCglibAopProxy(config);
    }

    @Override
    public Object getBean(Class<?> beanClass) throws Exception {
        return getBean(beanClass.getName());
    }

    public String[] getBeanDefinitionNames(){
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount(){
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig(){
        return this.reader.getConfig();
    }
}
